#include "stdafx.h"
#include <MyViews/ZoomView.h>
#include <MyViews/Resource.h>
#include <math/round.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// ZoomView

#pragma warning(push)
#pragma warning(disable: 4355) // 'this' used in base member initializer list

ZoomView::ZoomView()
	: m_ToolZoom(* this)
{
	m_BenchSize = extent<float>(0, 0);

	m_Scale = 1.0f;

	m_MinScale = 1.0f;
	m_MaxScale = 1.0f;
	m_YUp = false;
}

#pragma warning(pop)

ZoomView::~ZoomView()
{
	SetActiveTool(0);
}

point<float> ZoomView::ClientToBench(CPoint p) const
{
	point<float> result;
	p = Base::ClientToBench(p);
	result.x = p.x / m_Scale;
	result.y = m_YUp ? (m_BenchSize.sy - p.y / m_Scale) : (p.y / m_Scale);
	return result;
}

CPoint ZoomView::BenchToClient(point<float> p) const
{
	CPoint result;
	result.x = round(p.x * m_Scale);
	result.y = round((m_YUp ? m_BenchSize.sy - p.y : p.y) * m_Scale);
	result = Base::BenchToClient(result);
	return result;
}

rect<float> ZoomView::ClientToBench(CRect r) const
{
	r = Base::ClientToBench(r);
	rect<float> result;
	result.x1 = r.left / m_Scale;
	result.y1 = m_YUp ? (m_BenchSize.sy - r.bottom / m_Scale) : (r.top / m_Scale);
	result.x2 = r.right / m_Scale;
	result.y2 = m_YUp ? (m_BenchSize.sy - r.top / m_Scale) : (r.bottom / m_Scale);
	return result;
}

CRect ZoomView::BenchToClient(rect<float> r) const
{
	CRect result;
	result.left = (int) floor(r.x1 * m_Scale);
	result.top = (int) floor((m_YUp ? m_BenchSize.sy - r.y2 : r.y1) * m_Scale);
	result.right = (int) ceil(r.x2 * m_Scale);
	result.bottom = (int) ceil((m_YUp ? m_BenchSize.sy - r.y1 : r.y2) * m_Scale);
	result = Base::BenchToClient(result);
	return result;
}

CRect ZoomView::BenchToClient(CRect r) const
{
	return BenchToClient(rect<float>(float(r.left), float(r.top), float(r.right), float(r.bottom)));
}

rect<float> ZoomView::BenchToClient2(rect<float> r) const
{
	rect<float> result;
	result.x1 = r.x1 * m_Scale;
	result.y1 = (m_YUp ? m_BenchSize.sy - r.y2 : r.y1) * m_Scale;
	result.x2 = r.x2 * m_Scale;
	result.y2 = (m_YUp ? m_BenchSize.sy - r.y1 : r.y2) * m_Scale;

	result.x1 += m_Gap - m_Origin.x;
	result.y1 += m_Gap - m_Origin.y;
	result.x2 += m_Gap - m_Origin.x;
	result.y2 += m_Gap - m_Origin.y;

	return result;
}

void ZoomView::BestWholeMapping(CRect & c, CRect & b, unsigned e) const
{
	bool swap_x = c.Width() < 0;
	if (swap_x)
		std::swap(c.left, c.right);

	bool swap_y = c.Height() < 0;
	if (swap_y)
		std::swap(c.top, c.bottom);

	rect<float> r = ClientToBench(c);
	float e_left   = r.x1 - floor(r.x1);
	float e_top    = r.y1 - floor(r.y1);
	float e_right  = ceil(r.x2) - r.x2;
	float e_bottom = ceil(r.y2) - r.y2;

	CRect t = c;

	unsigned i;
	for (i = 0; i != e; ++i) {
		--t.left;
		++t.right;
		--t.top;
		++t.bottom;
		r = ClientToBench(t);
		float e = r.x1 - floor(r.x1);
		if (e < e_left) {
			e_left = e;
			c.left = t.left;
		}
		e = r.y1 - floor(r.y1);
		if (e < e_top) {
			e_top = e;
			c.top = t.top;
		}
		e = ceil(r.x2) - r.x2;
		if (e < e_right) {
			e_right = e;
			c.right = t.right;
		}
		e = ceil(r.y2) - r.y2;
		if (e < e_bottom) {
			e_bottom = e;
			c.bottom = t.bottom;
		}
	}

	r = ClientToBench(c);
	b.left  = LONG(floor(r.x1));
	b.top   = LONG(floor(r.y1));
	b.right = LONG(ceil(r.x2));
	b.bottom= LONG(ceil(r.y2));

	if (swap_x) {
		std::swap(c.left, c.right);
		std::swap(b.left, b.right);
	}

	if (swap_y) {
		std::swap(c.top, c.bottom);
		std::swap(b.top, b.bottom);
	}
}

void ZoomView::SetScale(float scale)
{
	if (scale < m_MinScale)
		scale = m_MinScale;
	if (scale > m_MaxScale)
		scale = m_MaxScale;
	if (scale != m_Scale) {
		m_Scale = scale;
		UpdateBenchSize();
		OnScale();
	}
}

void ZoomView::UpdateBenchSize()
{								   
	CSize s;
	s.cx = (int) ceil(m_BenchSize.sx * m_Scale);
	s.cy = (int) ceil(m_BenchSize.sy * m_Scale);
	Base::ResizeBench(s);
}

void ZoomView::ResizeBench(extent<float> s)
{
	if (s != m_BenchSize) {
		m_BenchSize = s;
		UpdateBenchSize();
	}
}

void ZoomView::Superpose(point<float> bench, CPoint client)
{
	CPoint old = BenchToClient(bench);
	SetOrigin(GetOrigin() + old - client);
}
BEGIN_MESSAGE_MAP(ZoomView, PanView)
	ON_COMMAND(ID_TOOL_ZOOM, OnZoomTool)
	ON_UPDATE_COMMAND_UI(ID_TOOL_ZOOM, OnUpdateZoomTool)
	ON_COMMAND(ID_ZOOM_VIEW_ZOOM_IN, OnZoomIn)
	ON_UPDATE_COMMAND_UI(ID_ZOOM_VIEW_ZOOM_IN, OnUpdateZoomIn)
	ON_COMMAND(ID_ZOOM_VIEW_ZOOM_OUT, OnZoomOut)
	ON_UPDATE_COMMAND_UI(ID_ZOOM_VIEW_ZOOM_OUT, OnUpdateZoomOut)
	ON_COMMAND(ID_ZOOM_VIEW_FIT_ON_SCREEN, OnFitOnScreen)
	ON_UPDATE_COMMAND_UI(ID_ZOOM_VIEW_FIT_ON_SCREEN, OnUpdateFitOnScreen)
	ON_COMMAND(ID_ZOOM_VIEW_ACTUAL_PIXELS, OnActualPixels)
	ON_UPDATE_COMMAND_UI(ID_ZOOM_VIEW_ACTUAL_PIXELS, OnUpdateActualPixels)
	ON_WM_SYSKEYDOWN()
	ON_WM_SYSKEYUP()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
END_MESSAGE_MAP()

CPoint ZoomView::Center()
{
	CRect r;
	GetClientRect(&r);
	return r.CenterPoint();
}

void ZoomView::Zoom(float scale, CPoint * p)
{
	CPoint pClient = (p != 0 ? * p : Center());
	point<float> pBench = ClientToBench(pClient);
	SetScale(scale);
	Superpose(pBench, pClient);
}

void ZoomView::ZoomBy(float factor, CPoint * p)
{
	Zoom(GetScale() * factor, p);
}

void ZoomView::ZoomFit()
{
	extent<float> bench = GetBenchSize();
	CSize client = GetClientSize();

	client.cx -= 2 * GetGap();
	client.cy -= 2 * GetGap();

	float fScaleX = 1.0f, fScaleY = 1.0f;
	if (bench.sx != 0)
		fScaleX = float(client.cx) / bench.sx;
	if (bench.sy != 0)
		fScaleY = float(client.cy) / bench.sy;

	float fScale = std::min(fScaleX, fScaleY);
	if (GetScale() != fScale) 
	{
		SetScale(fScale);
		ZoomFit();
	}
}

void ZoomView::OnZoomTool()
{
	SetActiveTool(GetZoomTool());
}

void ZoomView::OnUpdateZoomTool(CCmdUI *pCmdUI)
{
	pCmdUI->Enable(!IsBenchEmpty());
	pCmdUI->SetCheck(GetActiveTool() == GetZoomTool());
	pCmdUI->SetText("Zoom (Z)");
}

void ZoomView::OnZoomIn() 
{
	CSize s = GetClientSize();
	GetZoomTool()->ZoomIn(CPoint(s.cx/2, s.cy/2));
}

void ZoomView::OnUpdateZoomIn(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!IsBenchEmpty());
}

void ZoomView::OnZoomOut() 
{
	CSize s = GetClientSize();
	GetZoomTool()->ZoomOut(CPoint(s.cx/2, s.cy/2));
}

void ZoomView::OnUpdateZoomOut(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!IsBenchEmpty());
}

void ZoomView::OnFitOnScreen() 
{
	GetZoomTool()->ZoomFit();
}

void ZoomView::OnUpdateFitOnScreen(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!IsBenchEmpty());
}

void ZoomView::OnActualPixels() 
{
	SetScale(1.0f);
}

void ZoomView::OnUpdateActualPixels(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!IsBenchEmpty());
	pCmdUI->SetCheck(GetScale() == 1.0f);
}

void ZoomView::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	UpdateAltTool();
	Base::OnSysKeyDown(nChar, nRepCnt, nFlags);
}

void ZoomView::OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	UpdateAltTool();
	Base::OnSysKeyUp(nChar, nRepCnt, nFlags);
}

void ZoomView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	UpdateAltTool();
	Base::OnKeyDown(nChar, nRepCnt, nFlags);
}

void ZoomView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	UpdateAltTool();
	Base::OnKeyUp(nChar, nRepCnt, nFlags);
}

void ZoomView::UpdateAltTool()
{
	DWORD nControl = GetAsyncKeyState(VK_CONTROL);
	DWORD nAlt = GetAsyncKeyState(VK_MENU);
	DWORD nSpace = GetAsyncKeyState(VK_SPACE);

	bool bControl = (nControl != 0 || nSpace != 0);
	bool bAlt = (nAlt != 0);

	Tool * pAltTool = 0;

	if (bControl) {
		if (bAlt)
			pAltTool = GetZoomTool();
		else
			pAltTool = GetPanTool();
	}

	SetAltTool(pAltTool);
}

void ZoomView::CreateEnhMetaFile(LPCTSTR lpFilename, LPCTSTR lpDescription, float scale)
{
	CPoint SavedOrigin = m_Origin;
	m_Origin = CPoint(0, 0);
	float SavedScale = m_Scale;
	m_Scale = scale;

	try {
		CClientDC ClientDC(this);
		CMetaFileDC MetaDC;
		CRect Bounds(0, 0, scale * m_BenchSize.sx, scale * m_BenchSize.sy);
		VERIFY(MetaDC.CreateEnhanced(&ClientDC, lpFilename, 
			0, //here you may give Bounds converted from display pixels to .01 millimeters
			lpDescription));
		PaintRect(&MetaDC, &Bounds);
		VERIFY(DeleteEnhMetaFile(MetaDC.CloseEnhanced()));
	}
	catch(...) {
		m_Scale = SavedScale;
		m_Origin = SavedOrigin;
		throw;
	}

	m_Scale = SavedScale;
	m_Origin = SavedOrigin;
}
